<!DOCTYPE html>
<meta charset="utf-8">
<title>Declarative Shadow DOM</title>
<link rel="author" href="mailto:masonf@chromium.org">
<link rel="help" href="https://github.com/whatwg/dom/issues/831">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>

<div id="host" style="display:none">
  <template shadowrootmode="open">
    <slot id="s1" name="slot1"></slot>
  </template>
  <div id="c1" slot="slot1"></div>
</div>

<script>

// Uncaught exceptions (which will manifest as test harness
// errors) constitute a failure of this test. No parsing
// operations, whether imperative (setHTMLUnsafe) or declarative
// (main document parsing) should ever throw exceptions.
// More context:
// https://github.com/whatwg/html/issues/10527#issuecomment-2275253439

test(() => {
  const host = document.querySelector('#host');
  const c1 = host.querySelector('#c1');
  assert_true(!!c1);
  assert_equals(host.querySelector('#s1'), null, "Should be inside shadow root");
  assert_equals(host.querySelector('template'), null, "No leftover template node");
  assert_true(!!host.shadowRoot,"No shadow root found");
  const s1 = host.shadowRoot.querySelector('#s1');
  assert_equals(c1.assignedSlot, s1);
  assert_array_equals(s1.assignedNodes(), [c1]);
}, 'Declarative Shadow DOM: Basic test');

test(() => {
  assert_true(HTMLTemplateElement.prototype.hasOwnProperty("shadowRootMode"),'Unable to feature detect');
}, 'Declarative Shadow DOM: Feature detection');

test(() => {
  const t = document.createElement('template');
  t.setAttribute('shadowrootmode','open');
  assert_equals(t.shadowRootMode,'open','The shadowRootMode IDL should reflect the content attribute');
  t.setAttribute('shadowrootmode','closed');
  assert_equals(t.shadowRootMode,'closed','"open" and "closed" should both be valid values');
  t.setAttribute('shadowrootmode','OpEn');
  assert_equals(t.shadowRootMode,'open','Case insensitive');
  t.setAttribute('shadowrootmode','INVALID');
  assert_equals(t.shadowRootMode,'','Invalid values map to empty string');
  t.removeAttribute('shadowrootmode');
  assert_equals(t.shadowRootMode,'','No shadowrootmode attribute maps to empty string');
}, 'Shadowrootmode reflection');

test(() => {
  const t = document.createElement('template');
  t.shadowRootMode = 'blah';
  assert_equals(t.shadowRootMode, '');
  t.getAttribute('shadowrootmode', 'blah');
  t.shadowRootMode = 'CLOSED';
  assert_equals(t.shadowRootMode, 'closed');
  t.getAttribute('shadowrootmode', 'CLOSED');
}, 'Shadowrootmode reflection, setter');

test(() => {
  const t = document.createElement('template');
  t.setAttribute('shadowrootdelegatesfocus','');
  assert_equals(t.shadowRootDelegatesFocus,true,'The shadowRootDelegatesFocus IDL should reflect the content attribute');
  t.setAttribute('shadowrootdelegatesfocus','foobar');
  assert_equals(t.shadowRootDelegatesFocus,true,'The value doesn\'t matter');
  t.removeAttribute('shadowrootdelegatesfocus');
  assert_equals(t.shadowRootDelegatesFocus,false,'No shadowRootDelegatesFocus attribute maps to false');
}, 'Shadowrootdelegatesfocus reflection');

test(() => {
  const t = document.createElement('template');
  assert_equals(t.getAttribute('shadowrootdelegatesfocus'), null);
  t.shadowRootDelegatesFocus = true;
  assert_equals(t.getAttribute('shadowrootdelegatesfocus'), '');
  assert_equals(t.shadowRootDelegatesFocus, true);
  t.shadowRootDelegatesFocus = false;
  assert_equals(t.getAttribute('shadowrootdelegatesfocus'), null);
  assert_equals(t.shadowRootDelegatesFocus, false);
}, 'Shadowrootdelegatesfocus reflection, setter');


test(() => {
  const t = document.createElement('template');
  t.setAttribute('shadowrootclonable','');
  assert_equals(t.shadowRootClonable,true,'The shadowRootClonable IDL should reflect the content attribute');
  t.setAttribute('shadowrootclonable','foobar');
  assert_equals(t.shadowRootClonable,true,'The value doesn\'t matter');
  t.removeAttribute('shadowrootclonable');
  assert_equals(t.shadowRootClonable,false,'No shadowRootClonable attribute maps to false');
}, 'Shadowrootclonable reflection');

test(() => {
  const t = document.createElement('template');
  assert_equals(t.getAttribute('shadowrootclonable'), null);
  t.shadowRootClonable = true;
  assert_equals(t.getAttribute('shadowrootclonable'), '');
  assert_equals(t.shadowRootClonable, true);
  t.shadowRootClonable = false;
  assert_equals(t.getAttribute('shadowrootclonable'), null);
  assert_equals(t.shadowRootClonable, false);
}, 'Shadowrootclonable reflection, setter');

test(() => {
  const div = document.createElement('div');
  div.setHTMLUnsafe(`
    <div id="host">
      <template shadowrootmode="open">
        <slot id="s1" name="slot1"></slot>
      </template>
      <div id="c1" slot="slot1"></div>
    </div>
    `);
  const host = div.querySelector('#host');
  const c1 = host.querySelector('#c1');
  assert_true(!!c1);
  assert_equals(host.querySelector('#s1'), null, "Should be inside shadow root");
  assert_equals(host.querySelector('template'), null, "No leftover template node");
  assert_true(!!host.shadowRoot,"No shadow root found");
  const s1 = host.shadowRoot.querySelector('#s1');
  assert_equals(c1.assignedSlot, s1);
  assert_array_equals(s1.assignedNodes(), [c1]);
}, 'Declarative Shadow DOM: Fragment parser basic test');

test(() => {
  const div = document.createElement('div');
  div.setHTMLUnsafe(`
    <div id="host">
      <template shadowrootmode="invalid">
      </template>
    </div>
    `);
  const host = div.querySelector('#host');
  assert_equals(host.shadowRoot, null, "Shadow root was found");
  const tmpl = host.querySelector('template');
  assert_true(!!tmpl,"Template should still be present");
  const shadowrootAttr = tmpl.getAttribute('shadowrootmode');
  assert_equals(shadowrootAttr,"invalid","'shadowrootmode' attribute should still be present");
}, 'Declarative Shadow DOM: Invalid shadow root attribute');

test(() => {
  const div = document.createElement('div');
  div.setHTMLUnsafe(`
    <div id="host">
      <template shadowrootmode="closed">
      </template>
    </div>
    `);
  const host = div.querySelector('#host');
  assert_equals(host.shadowRoot, null, "Closed shadow root");
  assert_equals(host.querySelector('template'), null, "No template - converted to shadow root");
}, 'Declarative Shadow DOM: Closed shadow root attribute');

test(() => {
  const div = document.createElement('div');
  div.setHTMLUnsafe(`
    <div id="host">
      <template shadowrootmode="open">
        <slot id="s1" name="slot1"></slot>
    </div>
    `);
  const host = div.querySelector('#host');
  assert_equals(host.querySelector('#s1'), null, "Should be inside shadow root");
  assert_equals(host.querySelector('template'), null, "No leftover template node");
  assert_true(!!host.shadowRoot,"No shadow root found");
  const s1 = host.shadowRoot.querySelector('#s1');
  assert_true(!!s1,"Slot should be inside the shadow root");
}, 'Declarative Shadow DOM: Missing closing tag');

test(() => {
  const div = document.createElement('div');
  div.setHTMLUnsafe(`
    <div id="host">
      <template shadowrootmode="open" shadowrootdelegatesfocus>
      </template>
    </div>
    `);
  var host = div.querySelector('#host');
  assert_true(!!host.shadowRoot,"No shadow root found");
  assert_true(host.shadowRoot.delegatesFocus,"delegatesFocus should be true");
  div.setHTMLUnsafe(`
    <div id="host">
      <template shadowrootmode="open">
      </template>
    </div>
    `);
  host = div.querySelector('#host');
  assert_true(!!host.shadowRoot,"No shadow root found");
  assert_false(host.shadowRoot.delegatesFocus,"delegatesFocus should be false without the shadowrootdelegatesfocus attribute");
}, 'Declarative Shadow DOM: delegates focus attribute');

test(() => {
  const div = document.createElement('div');
  div.setHTMLUnsafe(`
    <div id="host">
      <template shadowrootmode="open" shadowrootclonable>
      </template>
    </div>
    `);
  var host = div.querySelector('#host');
  assert_true(!!host.shadowRoot,"No shadow root found");
  assert_true(host.shadowRoot.clonable,"clonable should be true");
  div.setHTMLUnsafe(`
    <div id="host">
      <template shadowrootmode="open">
      </template>
    </div>
    `);
  host = div.querySelector('#host');
  assert_true(!!host.shadowRoot,"No shadow root found");
  assert_false(host.shadowRoot.clonable,"clonable should be false without the shadowrootclonable attribute");
}, 'Declarative Shadow DOM: clonable attribute');
</script>

<div id="multi-host" style="display:none">
  <template shadowrootmode="open">
    <span>root 1</span>
  </template>
  <template shadowrootmode="closed">
    <span>root 2</span>
  </template>
</div>
<script>
test(() => {
  const host = document.querySelector('#multi-host');
  const leftover = host.querySelector('template');
  assert_true(!!leftover, "The second (duplicate) template should be left in the DOM");
  assert_true(leftover instanceof HTMLTemplateElement);
  assert_equals(leftover.getAttribute('shadowrootmode'),"closed");
  assert_equals(leftover.shadowRootMode,"closed");
  assert_true(!!host.shadowRoot,"No open shadow root found - first root should remain");
  const innerSpan = host.shadowRoot.querySelector('span');
  assert_equals(innerSpan.textContent, 'root 1', "Content should come from first declarative shadow root");
}, 'Declarative Shadow DOM: Multiple roots');

</script>

<template id="template-containing-shadow">
  <div class="innerdiv">
    <template shadowrootmode=open shadowrootclonable>Content</template>
  </div>
</template>
<script>
test(() => {
  const template = document.querySelector('#template-containing-shadow');
  const container1 = document.createElement('div');
  container1.style.display = 'none';
  document.body.appendChild(container1);
  container1.appendChild(template.content.cloneNode(true));
  let innerDiv = container1.querySelector('div.innerdiv');
  const shadowRoot1 = innerDiv.shadowRoot;
  assert_true(!!shadowRoot1,"Inner div should have a shadow root");
  assert_equals(innerDiv.querySelector('template'), null, "No leftover template node");

  const container2 = document.createElement('div');
  container2.style.display = 'none';
  document.body.appendChild(container2);
  container2.appendChild(template.content.cloneNode(true));
  innerDiv = container2.querySelector('div.innerdiv');
  const shadowRoot2 = innerDiv.shadowRoot;
  assert_true(!!shadowRoot2,"Inner div should have a shadow root");
  assert_equals(innerDiv.querySelector('template'), null, "No leftover template node");

  assert_not_equals(shadowRoot1,shadowRoot2,'Should not get back the same shadow root');

  // Make sure importNode also works.
  const container3 = document.createElement('div');
  container3.style.display = 'none';
  document.body.appendChild(container3);
  container3.appendChild(document.importNode(template.content,true));
  innerDiv = container3.querySelector('div.innerdiv');
  const shadowRoot3 = innerDiv.shadowRoot;
  assert_true(!!shadowRoot3,"Inner div should have a shadow root");
  assert_equals(innerDiv.querySelector('template'), null, "No leftover template node");
  assert_not_equals(shadowRoot1,shadowRoot3,'Should not get back the same shadow root');

}, 'Declarative Shadow DOM: template containing declarative shadow root (with shadowrootclonable)');
</script>

<template id="template-containing-deep-shadow">
  <div><div><div><div><div>
    <div class="innerdiv">
      <template shadowrootmode=open shadowrootclonable>Content</template>
    </div>
  </div></div></div></div></div>
</template>
<script>
test(() => {
  const template = document.querySelector('#template-containing-deep-shadow');
  const host = document.createElement('div');
  host.style.display = 'none';
  document.body.appendChild(host);
  host.appendChild(template.content.cloneNode(true));
  assert_true(!!host.querySelector('div.innerdiv').shadowRoot,"Inner div should have a shadow root");
}, 'Declarative Shadow DOM: template containing (deeply nested) declarative shadow root');
</script>

<template id="template-containing-template">
  <div>
    <template id="inner-template">
      <div class="innerdiv">
        <template shadowrootmode=open shadowrootclonable>Content</template>
      </div>
    </template>
  </div>
</template>
<script>
test(() => {
  const template = document.querySelector('#template-containing-template');
  const host = document.createElement('div');
  host.style.display = 'none';
  document.body.appendChild(host);
  host.appendChild(template.content.cloneNode(true));
  const innerTemplate = host.querySelector('#inner-template');
  assert_true(!!innerTemplate.content.querySelector('div.innerdiv').shadowRoot,"Inner div should have a shadow root");
}, 'Declarative Shadow DOM: template containing a template containing declarative shadow root');
</script>

<template id="template-containing-ua-shadow">
  <div class="innerdiv">
    <template shadowrootmode=open shadowrootclonable>
      <video></video> <!--Assumed to have UA shadow root-->
    </template>
  </div>
</template>
<script>
test(() => {
  const template = document.querySelector('#template-containing-ua-shadow');
  const host = document.createElement('div');
  host.style.display = 'none';
  document.body.appendChild(host);
  // Mostly make sure clone of template *does* clone the
  // shadow root, and doesn't crash on cloning the <video>.
  host.appendChild(template.content.cloneNode(true));
  let innerDiv = host.querySelector('div.innerdiv');
  const shadowRoot = innerDiv.shadowRoot;
  assert_true(!!shadowRoot,"Inner div should have a shadow root");
  assert_equals(innerDiv.querySelector('template'), null, "No leftover template node");
  assert_true(!!innerDiv.shadowRoot.querySelector('video'),'Video element should be present');
}, 'Declarative Shadow DOM: template containing declarative shadow root and UA shadow root');
</script>

<template id="template-containing-ua-shadow-closed">
  <div class="innerdiv">
    <template shadowrootmode=closed>
      <video></video> <!--Assumed to have UA shadow root-->
    </template>
  </div>
</template>
<script>
test(() => {
  const template = document.querySelector('#template-containing-ua-shadow-closed');
  const host = document.createElement('div');
  host.style.display = 'none';
  document.body.appendChild(host);
  host.appendChild(template.content.cloneNode(true));
  let innerDiv = host.querySelector('div.innerdiv');
  assert_true(!innerDiv.shadowRoot,"Inner div should have a closed shadow root");
}, 'Declarative Shadow DOM: template containing closed declarative shadow root and UA shadow root');
</script>

<template id="root-element-shadow">
    <template shadowrootmode=open>Content</template>
</template>
<script>
test(() => {
  // Root element of this template is a <template shadowroot>:
  const template = document.querySelector('#root-element-shadow');
  const host = document.createElement('div');
  host.appendChild(template.content.cloneNode(true));
  assert_equals(host.shadowRoot, null, "Shadow root should not be present");
  const tmpl = host.querySelector('template');
  assert_true(!!tmpl,"Template should still be present");
  assert_equals(tmpl.getAttribute('shadowrootmode'),"open","'shadowrootmode' attribute should still be present");
}, 'Declarative Shadow DOM: declarative shadow roots are not supported by the template element');
</script>

<script>
  let gotError = false;
  window.addEventListener('error',() => {gotError = true;});
</script>
<progress id="invalid-element-exception">
  <template shadowrootmode=open>Content</template>
</progress>
<script>
test(() => {
  assert_false(gotError,'Exceptions should not be thrown by the parser');
  const host = document.querySelector('#invalid-element-exception');
  const leftover = host.querySelector('template');
  assert_true(!host.shadowRoot,"Progress elements don't allow shadow roots");
  assert_true(!!leftover, "The template should be left in the DOM");
  // This also should not throw exceptions:
  const div = document.createElement('div');
  document.body.appendChild(div);
  div.setHTMLUnsafe('<progress><template shadowrootmode=open></template></progress>');
  assert_false(gotError,'Exceptions should not be thrown by the parser');
  assert_true(!!div.querySelector('template'),'parsing should have succeeded and left the template child');
  host.remove();
  div.remove();
}, 'Declarative Shadow DOM: explicit test that exceptions are not thrown');
</script>
